/******************  Universal EventLogging utility  ****************
 **                                                                **
 *                    Copyright (c) 2007/1/27 by yang               *
 ********************************************************************/
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <stdlib.h>
#include <sys/types.h> 
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <time.h>

/* platform dependence */
#if defined(_WIN32) || defined(__WIN32__) || defined(WIN32)
#include <windows.h>
#include <process.h>
#include <io.h>
#include <sys/locking.h>
typedef CRITICAL_SECTION unilog_lock_t;
#define unilog_lock_init(l) InitializeCriticalSection(l)
#define unilog_lock_destroy(l) DeleteCriticalSection(l)
#define unilog_lock(l) EnterCriticalSection(l)
#define unilog_unlock(l) LeaveCriticalSection(l)
#define unilog_lockf _locking
#define unilog_truncate chsize
#define unilog_stricmp stricmp
#define unilog_vsnprintf _vsnprintf
#else
#include <unistd.h>
#include <pthread.h>
#include <strings.h>
typedef pthread_mutex_t unilog_lock_t;
#define unilog_lock_init(l) pthread_mutex_init(l, 0)
#define unilog_lock_destroy(l) pthread_mutex_destroy(l)
#define unilog_lock(l) pthread_mutex_lock(l)
#define unilog_unlock(l) pthread_mutex_unlock(l)
#define unilog_lockf lockf
#define unilog_lockf lockf
#define unilog_truncate ftruncate
#define unilog_stricmp strcasecmp
#define unilog_vsnprintf vsnprintf
#endif

#include "unilog.h"

typedef struct {
    int level;
    int type;
    char flag;
} ulMsgType;

static const ulMsgType log_msgmap[] =
{
    /*ll_FATAL      0*/ {ll_FATAL,   0, 'F'},
    /*ll_ERROR      1*/ {ll_ERROR,   0, 'E'},
    /*ll_WARNING    2*/ {ll_WARNING, 1, 'W'},
    /*ll_MESSAGE    3*/ {ll_MESSAGE, 2, 'M'},
    /*ll_INFO       4*/ {ll_INFO,    2, 'I'},
    /*ll_NOTICE     5*/ {ll_NOTICE,  2, 'N'},
    /*ll_TRACE      6*/ {ll_TRACE,   2, 'T'},
    /*ll_DEBUG      7*/ {ll_DEBUG,   2, 'D'},
};

struct unilog
{
    int    level;
    int    fd;
    int    max_kb;
    char   title[32];
    char   storage[300];
    int    status;
    struct unilog *iam;
    unilog_lock_t lk;
};


#if   0 <= USE_LOG

/**
*   unilog_check
*/
int unilog_check(unilog *log)
{
    if (!log || log != log->iam) return -1;
    return 0;
}

/**
*   unilog_open
*/
unilog *unilog_open(const char *fname, const char *title,
                    int max_kb, int level)
{
    unilog *log;
    
    if (!fname || !*fname)
    {
        return NULL;
    }
    
    log = (unilog*)malloc(sizeof(unilog));
    if (!log) return NULL;
    
    log->level = ll_NOTICE;
    log->fd = -1;
    log->max_kb = -1;
    log->title[0] = '\0';
    log->storage[0] = '\0';
    log->status = -1;
    unilog_lock_init(&log->lk);
    log->iam = log;
    
    unilog_reopen(log, fname, title, max_kb, level);
    return log;
}


void unilog_close(unilog *log)
{
    int old_fd;
    
    if (!log || log->iam != log) 
        return;
    
    unilog_lock(&log->lk);
    if (log->iam != log)
        unilog_unlock(&log->lk);
    else
    {
        log->iam = NULL;
        old_fd = log->fd;
        log->fd = -1;
        if (old_fd != -1)
            close(old_fd);
        unilog_unlock(&log->lk);
        
        unilog_lock_destroy(&log->lk);
        free(log);
    }
}


unilog *unilog_reopen(unilog *base,
                      const char *fname, const char *title,
                      int max_kb, int level)
{
    char storage[FILENAME_MAX+16];
    size_t len;
    int rc = 0;
    int fd;

    if (!base)
      return unilog_open(fname, title, max_kb, level);
    
    if (base->iam != base) return NULL;

    if (!fname)
    {
        fname = base->storage;
    }
    
    unilog_lock(&base->lk);
   
    if (unilog_stricmp(fname, base->storage))   // name changed
    {
        int old_fd = base->fd;
        len = strlen(fname);
        if (len > sizeof(storage)-1) 
            len = sizeof(storage)-1;
        memcpy(storage, fname, len);
        storage[len] = '\0';
        
#if defined(_WIN32) || defined(__WIN32__) || defined(WIN32)
        fd = open(storage, O_RDWR|O_BINARY|O_CREAT|(max_kb ? 0 : O_TRUNC), 
                            S_IREAD|S_IWRITE);
#else
        fd = open(storage, O_RDWR|O_CREAT|(max_kb ? 0 : O_TRUNC), 
                            S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
#endif
        if (0 <= fd)
        {
            base->fd = fd;
            base->status = 0;
            rc = 1;
        }else {
            rc = -1;
        }

        if (rc > 0)
        {
            base->storage[sizeof(base->storage)-1] = '\0';
            if (base->storage != fname)
                strncpy(base->storage, fname, sizeof(base->storage)-1);
            if (old_fd != -1) 
                close(old_fd);
        }
    }
    if (rc >= 0)
    {
        if (level >= ll_FATAL)
            base->level = (level <= ll_DEBUG ? level: ll_DEBUG);
        
        if (max_kb >= -1)
            base->max_kb = max_kb;
        if (title && strncmp(title, base->title, sizeof(base->title)))
        {
            base->title[sizeof(base->title)-1] = '\0';
            strncpy(base->title, title, sizeof(base->title)-1);
        }
    }
    
    unilog_unlock(&base->lk);
    
    return (rc >= 0 ? base: NULL);
}

/*
static void remove_np(char *buff, size_t size)
{
    while(size--)
    {
        if (' ' > (unsigned char)*buff) *buff = ' ';
        buff++;
    }
}*/


static size_t ul_snprintf(char *buf, size_t size, const char *fmt, ...)
{
    int len;
    va_list arg;
    va_start(arg, fmt);
    len = unilog_vsnprintf(buf, size, fmt, arg);
    va_end(arg);
    return (0 > len || size <= (size_t)len) ? 0 : (size_t)len;
}


void unilog_v(int level, unilog *log, const char *title, const char *fmt, va_list arg)
{
    char buff[4096];
    int len;
    long pos_lk = -1, pos_e;

#if defined(_WIN32) || defined(__WIN32__) || defined(WIN32)
    SYSTEMTIME ti;
#else
    time_t t;
    struct tm ts;
#endif
    
/* "1999/02/23 13:15:59.003 x"
    0123456789012345678901234 */

#define MSG_TIMESTAMP (20)
#define MSG_START (MSG_TIMESTAMP)
    size_t size = MSG_START;
#define BUF_SIZE_ARG (sizeof(buff)-20-size)
#define BUF_LEN_ARG &buff[size],BUF_SIZE_ARG
    
    if (!fmt || level < ll_FATAL || level >= (int)SIZEOF_ARRAY(log_msgmap)) 
        return;
    
    if (!log || log != log->iam || log->status == -1)
        return;
    
    /* level: */
    if (log_msgmap[level].level > log->level && level != ll_FATAL)
        return;

    //size += ul_snprintf(BUF_LEN_ARG, "%c%-2d",
    //    log_msgmap[level].flag, log_msgmap[level].level);
    size += ul_snprintf(BUF_LEN_ARG, "%c ", log_msgmap[level].flag);

    /* title: */
    if (!title) title = log->title;
    if (*title)
        size += ul_snprintf(BUF_LEN_ARG, "%s ", title);
    
    /* msg: */
    len = unilog_vsnprintf(BUF_LEN_ARG, fmt, arg);
    if (len > 0 && len < (int)BUF_SIZE_ARG) size += len;
    
    //buff[size++] = '\r';
    buff[size++] = '\n';
    buff[sizeof(buff)-1] = '\0';
    
    /* timestamp: */
    memset(buff, ' ', MSG_START);
#if defined(_WIN32) || defined(__WIN32__) || defined(WIN32)
    GetLocalTime(&ti);
    len = ul_snprintf(buff, MSG_TIMESTAMP, "%04u-%02u-%02u %02u:%02u:%02u",
        0x1fff & ti.wYear, 0x3f & ti.wMonth, 0x3f & ti.wDay,
        0x3f & ti.wHour, 0x3f & ti.wMinute, 0x3f & ti.wSecond);
        //(0x3ff & ti.wMilliseconds) % 1000u);
#else
    time(&t);
    localtime_r(&t, &ts);
    len = ul_snprintf(buff, MSG_TIMESTAMP, "%04u-%02u-%02u %02u:%02u:%02u",
        0x1fff & (ts.tm_year+1900), 0x3f & (ts.tm_mon+1), 0x3f & ts.tm_mday,
        0x3f & ts.tm_hour, 0x3f & ts.tm_min, 0x3f & ts.tm_sec);
#endif
    if (len > 0) {
        while((int)len < MSG_START) 
            buff[len++] = ' ';
    }
    
    //remove_np(buff, size - 2);
    
    if (log->iam != log) return;
    
    unilog_lock(&log->lk);
    if (log->iam != log) goto Leave;
    
    pos_e = lseek(log->fd, 0L, SEEK_END);
    write(log->fd, buff, size);
    
    // truncate when file size > max_kb 
    if (log->max_kb > 0 && ((long)log->max_kb << 10) < (pos_e += size))
    {
        char *nl_aligned = 0;
        long pos_s = 0L;
        pos_e = (long)log->max_kb << 9;
        
        /* lock file */
        pos_lk = lseek(log->fd, 1L<<16, SEEK_END);
#if defined(_WIN32) || defined(__WIN32__) || defined(WIN32)
        if (0 > pos_lk || 0 > unilog_lockf(log->fd, _LK_LOCK, -pos_lk)) 
#else
        if (0 > pos_lk || 0 > unilog_lockf(log->fd, F_LOCK, -pos_lk)) 
#endif          
            goto Leave;
        
        do {
            int wr_len;
            if (0 > lseek(log->fd, pos_e + pos_s, SEEK_SET) ||
                0 > (len = read(log->fd, buff, sizeof(buff))) ) break;
            wr_len = len;
            if (0 == nl_aligned)
            {
                nl_aligned = (char*)memchr(buff, '\n', len);
                if (nl_aligned)
                {
                    nl_aligned++;
                    wr_len -= nl_aligned - buff;
                    pos_e += nl_aligned - buff;
                }
                else wr_len = 0, pos_e += len;
            }
            if (wr_len && (
                0 > lseek(log->fd, 0 + pos_s, SEEK_SET) ||
                0 > write(log->fd, buff + len - wr_len, wr_len) ) ) break;
            pos_s += wr_len;
        } while (len == sizeof(buff));

        /* unlock file */
        if (pos_lk >= 0)
        {
            lseek(log->fd, pos_lk, SEEK_SET);
#if defined(_WIN32) || defined(__WIN32__) || defined(WIN32)
            unilog_lockf(log->fd, _LK_UNLCK, -pos_lk);
#else
            unilog_lockf(log->fd, F_UNLCK, -pos_lk);
#endif          
        }

        unilog_truncate(log->fd, pos_s);
    }

Leave:
    unilog_unlock(&log->lk);
    return;
}


void unilog_l(int level, unilog *log, const char *title, const char *fmt, ...)
{
    va_list arg;
    va_start(arg, fmt);
    unilog_v(level, log, title, fmt, arg);
    va_end(arg);
}


#define GEN_ULOG(level)                                  \
void unilog_##level(unilog *log, const char *title, const char *fmt, ...)   \
{ va_list arg; va_start(arg, fmt);                       \
unilog_v(ll_##level, log, title, fmt, arg);  va_end(arg); }

GEN_ULOG(FATAL)
GEN_ULOG(ERROR)
GEN_ULOG(WARNING)
GEN_ULOG(MESSAGE)
GEN_ULOG(INFO)
GEN_ULOG(NOTICE)
GEN_ULOG(TRACE)
GEN_ULOG(DEBUG)

#endif  /* USE_LOG >= 0 */

/* end of unilog.c */
